# !/usr/bin/env python
# -*- coding:utf-8 -*-
# author:ouyang time:2022/8/13

# import RPi.GPIO as GPIO
import json
import threading
import time
import base64
import os
import copy
import signal
from collections import deque
"""所有引脚的定义"""
# RGB三色灯引脚定义
LED_R = 22
LED_G = 27
LED_B = 24
# 风扇
FAN = 2
# 蜂鸣器
BEE = 8
# 电机控制引脚
IN1 = 20
IN2 = 21
IN3 = 19
IN4 = 26
ENA = 16
ENB = 13

# 超声波引脚定义
EchoPin = 0
TrigPin = 1


class CarController(threading.Thread):
    """
    接收信息及控制类
    """
    send_to_serve = {}  # 发送给服务端的数据
    temp_json = {}  # 中间缓存的数据
    json_buff = {}  # 缓存字典
    rgb_buff = {}  # RGB等数据缓存字典
    car_go_buff = {}  # 小车前进信息
    pic_recong_tsak_attr = {}  # 定义发送的任务特征字典数据， 包含任务id,三元组，以及内容

    def __init__(self, ip):
        """
        初始化所有的引脚信息
        :param ip:
        """
        self.IP = ip
        self.thread_run = 1
        self.offload_sta = 0  # 卸载状态， 状态为0则不卸载，即，本地处理。状态为1则处于卸载中，为-1说明已经请求结束
        self.offload_finish_sta = 0
        self.offload_task_count = 1  # 定义卸载任务数
        self.total_count = 2
        self.each_pic_count = 1  # 每次卸载的任务量是一样的，即一张照片
        self.t_max_high = 200
        self.t_max_low = 7
        self.offload_task_list = deque()
        self.timestamp = 0   # 定义任务时间戳
        self.offload_start_time = 0  # 记录任务开始的时间
        self.t_max_record = {}  # 记录每个任务的最大时延，用于统计失效率
        self.t_max = 4  # 定义最大时延
        self.x = 1  # 定义任务密度
        threading.Thread.__init__(self)

        self.list_lock = threading.Lock()
        # # 设置RGB三色灯为BCM编码方式
        # GPIO.setmode(GPIO.BCM)
        #
        # # RGB三色灯设置为输出模式
        # GPIO.setup(LED_R, GPIO.OUT)
        # GPIO.setup(LED_G, GPIO.OUT)
        # GPIO.setup(LED_B, GPIO.OUT)
        # GPIO.setup(FAN, GPIO.OUT)

        # 超声波的引脚初始化
        # GPIO.setup(EchoPin, GPIO.IN)
        # GPIO.setup(TrigPin, GPIO.OUT)
        #
        # # 电机控制初始化
        # GPIO.setup(ENA, GPIO.OUT, initial=GPIO.HIGH)
        # GPIO.setup(IN1, GPIO.OUT, initial=GPIO.LOW)
        # GPIO.setup(IN2, GPIO.OUT, initial=GPIO.LOW)
        # GPIO.setup(ENB, GPIO.OUT, initial=GPIO.HIGH)
        # GPIO.setup(IN3, GPIO.OUT, initial=GPIO.LOW)
        # GPIO.setup(IN4, GPIO.OUT, initial=GPIO.LOW)
        # # 设置pwm引脚和频率为2000hz
        # pwm_ENA = GPIO.PWM(ENA, 2000)
        # pwm_ENB = GPIO.PWM(ENB, 2000)
        # pwm_ENA.start(0)
        # pwm_ENB.start(0)

    def test(self):
        """
        类实例测试用
        :return:无
        """
        print("test is ok!, IP is : " + self.IP)

    def parse_json_data(self, json_data):
        """
        所有服务端传输过来的JSON数据，都在这里进行解析
        :param json_data: 服务端的jsons数据,在函数内部进行json数据解析
        :return: 无
        """
        print("data start prase")
        self.send_to_serve.clear() # 填装数据前先清除数据
        try:
            self.json_buff = json.loads(json_data)
            if self.json_buff.get('RGB', 0) != 0:
                self.rgb_buff = self.json_buff['RGB']
            if self.json_buff.get('FAN', 0) != 0:
                self.fan(self.json_buff['FAN'])
            if self.json_buff.get('DIS', 0) != 0:
                self.send_to_serve['DIS'] = distance
            if self.json_buff.get('GO', 0) != 0:
                self.car_go_buff = self.json_buff['GO']
        except Exception as info:
            print(info)
            self.send_to_serve['error'] = info  # 如果发生错误，则返回服务器错误信息

        finally:
            return self.send_to_serve  # 正常所有信息

    def rgb(self, sta):
        """
        :param sta: 由于此RGB只能控制开关，不能控制亮度，因此sta是一个三色灯的状态值,三个控制位；
        :return:无
        """
        # print("RGB:{0}, {1}, {2}", sta)
        # if sta.get('R',-1) != -1:
             # GPIO.output(LED_R, int(sta['R']))
        # if sta.get('G',-1) != -1:
             # GPIO.output(LED_G, int(sta['G']))
        # if sta.get('B',-1) != -1:
             # GPIO.output(LED_G, int(sta['B']))

    def distance_solve(self):
        """
        超声波测距
        :return: 超声波测得的距离
        """
        # GPIO.output(TrigPin, GPIO.HIGH)
        # time.sleep(0.000015)
        # GPIO.output(TrigPin, GPIO.LOW)
        # while not GPIO.input(EchoPin):
        #     pass
        # t1 = time.time()
        # while GPIO.input(EchoPin):
        #     pass
        # t2 = time.time()
        # print
        # "distance is %d " % (((t2 - t1) * 340 / 2) * 100)
        # time.sleep(0.01)
        # dis = ((t2 - t1) * 340 / 2) * 100
        dis = 25.123
        # print("the distance is :", dis)
        return round(dis, 2)

    def fan(self, str):
        print("FAN:{0}".format(int(str)))
        # GPIO.output(LED_B, int(str))

    def run(self):
        """
        重写线程的run函数
        :return:
        """
        self.car_main_task()

    def handler(self, signum, frame):
        self.car_stop()
        exit(1)

    def car_main_task(self):
        global distance
        self.get_timestamp()
        self.offload_start_time = self.timestamp
        time.sleep(2)
        while True:
            if self.thread_run == 1:
                try:
                    # self.forward_with_avoid()
                    # self.rgb(self.rgb_buff)
                    self.offload_task()
                except KeyboardInterrupt as info:
                    print("Car Thread error!")
                    print(info)
                    self.car_stop()
                    return 0
            else:
                return 0

    def warning(self, sta):
        if sta == 1:
            self.rgb_buff['R'] = 1
            self.rgb_buff['G'] = 0
            self.rgb_buff['B'] = 0
        else:
            self.rgb_buff['R'] = 0
            self.rgb_buff['G'] = 1
            self.rgb_buff['B'] = 0

    def forward_with_avoid(self):
        """
        小车前进加避障
        :return:
        """
        distance = self.distance_solve()
        if self.car_go_buff.get('STA') == '0':  # sta标志为0，则停止
            self.car_stop()
        else:
            if distance <= 10:
                self.warning(1)
                self.car_stop()
            else:
                self.warning(0)
                self.car_start(self.car_go_buff.get('SPEED', 1))
        time.sleep(1)

    def car_start(self, speed):
        global pwm_ENA
        global pwm_ENB
        self.warning(0)
        print("car has started !, speed is:", speed)
        # GPIO.output(IN1, GPIO.HIGH)
        # GPIO.output(IN2, GPIO.LOW)
        # GPIO.output(IN3, GPIO.HIGH)
        # GPIO.output(IN4, GPIO.LOW)
        # pwm_ENA.ChangeDutyCycle(speed)
        # pwm_ENB.ChangeDutyCycle(speed)

    def PID_direction(self, target,):
        print("test")

    def get_timestamp(self):
        self.timestamp = time.time()

    def car_stop(self):
        print("car has stopped !")
        self.warning(1)
        # GPIO.output(IN1, GPIO.LOW)
        # GPIO.output(IN2, GPIO.LOW)
        # GPIO.output(IN3, GPIO.LOW)
        # GPIO.output(IN4, GPIO.LOW)
        # pwm_ENA.ChangeDutyCycle(1)
        # pwm_ENB.ChangeDutyCycle(1)

    def stop_thread(self):
        self.thread_run = 0

    def offload_task(self):
        """
        所有的卸载任务会在这里汇总
        卸载任务说明：
        当每个任务量之间的差距不大时，小于3倍的量时，队列算法最快，大于3倍的容量时，
        :return:返回任务列表
        """
        while self.offload_task_count <= self.total_count:
            # 这里进行任务划分, 任务紧急度越高，相应的计算密度也低
            if self.offload_task_count % 10 < 2:
                self.t_max = self.t_max_low
                self.x = self.t_max

            elif self.offload_task_count % 10 < 4:
                self.t_max = self.t_max_high+2
                self.x = self.t_max*3

            elif self.offload_task_count % 10 < 6:
                self.t_max = self.t_max_high+1
                self.x = self.t_max*2

            elif self.offload_task_count % 10 < 8:
                self.t_max = self.t_max_low+5  # (self.total_count/40)*
                self.x = self.t_max*5

            temp = copy.deepcopy(self.car_pic_recong_task(self.each_pic_count))  # 这里必须分清楚深拷贝和浅拷贝

            self.offload_task_list.append(temp)

            self.offload_task_count += 1

            time.sleep(0.001)

    def car_pic_recong_task(self, each_pic_num):
        """
        图片识别任务结构, 字典里面的结构用列表存储
        {
          'id': 01,
          'head':  # 这个是整个任务的三元组，代表任务的id, 输入比特数，最大延时 ，计算密度
          [
           {'head_id' : 01 ,
           'head_body':
               [
                {'B_in' :  02 },
                {'t_max'  :  03 },
                {'X'  :  04  }
               ]
           }
          ]
          'body':  #  这里是具体的图片 id 和 图片二进制内容
          [
               {
                   'body_id' : 01 ,
                   'body_body':
                       [
                            {
                            'id': 'pic',
                            'id': 'pic'
                            ...
                            }
                   ]
           }
          ]
        }

        填充任务 head 数据
        :param each_pic_num: 总共需要卸载的任务数
        :return:任务结构
        """
        self.pic_recong_tsak_attr.clear()
        temp_dir_l3 = {}  # 第三层字典
        temp_dir_l2 = {}  # 第二层字典
        temp_list_l1 = []
        temp_list_l3 = []

        temp_dir_l3['request'] = 1
        temp_dir_l3['replay'] = 0  # replay为-1则代表卸载任务超时，被拒绝；
        temp_dir_l3['B_in'] = self.read_pic_bytes(each_pic_num)  # 每次卸载五张图片
        temp_dir_l3['t_max'] = self.t_max   # 以ms为单位
        temp_dir_l3['X'] = self.x  # 计算密度

        self.t_max_record[self.timestamp] = self.t_max  # 记录当前任务的id以及最大时延

        temp_list_l3.append(temp_dir_l3)
        temp_dir_l2['head_id'] = self.timestamp
        temp_dir_l2['head_body'] = temp_list_l3
        temp_list_l1.append(temp_dir_l2)
        self.pic_recong_tsak_attr['head'] = temp_list_l1
        self.pic_recong_tsak_attr['id'] = self.timestamp
        print(temp_list_l1)
        # print('id: t:', self.timestamp, self.t_max)

        return self.pic_recong_tsak_attr

    def read_pic_bytes(self, pic_count):
        """
        填充任务 body 数据
        :param pic_count: 单次卸载的图片数
        :return:
        """
        path = './data/MNIST/test'  # 定义图片路径
        temp_dir_l2 = {}  # 用于临时存储的字典, 注意字典也是一个对象，因此不能把字典依次加入队列中，即使加入队列，所有的数据都会一模一样
        temp_dir_l3 = {}
        temp_list_l1 = []  # 第一层的列表
        temp_list_l3 = []
        tmp_lenth = 0
        files = os.listdir(path)
        for file in files:
            if pic_count != 0:
                pic_file = open(path+'/'+file, 'rb')
                pic_bytes = pic_file.read()
                base64_data = base64.b64encode(pic_bytes)
                pic_str = str(base64_data, 'utf-8')
                temp_dir_l3[file] = pic_str
                tmp_lenth += len(pic_bytes)
                pic_count -= 1
            else:
                break
        temp_list_l3.append(temp_dir_l3)
        self.get_timestamp()  # 卸载任务之前先获取时间戳, 读取图片需要很长时间，因此不算在时间戳里面
        temp_dir_l2['body_id'] = self.timestamp
        temp_dir_l2['body_X'] = self.x
        temp_dir_l2['body_body'] = temp_list_l3
        temp_list_l1.append(temp_dir_l2)
        self.pic_recong_tsak_attr['body'] = temp_list_l1
        return tmp_lenth

    def get_offload_data(self):
        """
        外部调用接口，返回卸载任务
        :return:
        """
        temp_task = []
        count = len(self.offload_task_list)
        if count != 0:
            for i in range(0, count):

                temp_task.append(self.offload_task_list.popleft())
            print(temp_task)
            return temp_task
        else:
            return None

    def get_task_t_max(self):
        """
        :return:这里直接返回字典对象，字典对象会一直更新
        """
        if len(self.t_max_record) != 0:
            return self.t_max_record
        else:
            return None

    def get_start_time(self):
        """
        :return: 返回任务开始的时间
        """
        return self.offload_start_time


if __name__ == '__main__':
    car = CarController('0.0.0.0')
    car.start()
    time.sleep(1)
    car.get_offload_data()
    time.sleep(5)
    car.get_offload_data()

